package com.sharp.join.tk.mybatis.framework.join;

import com.sharp.join.tk.mybatis.framework.join.anno.JoinAssociation;
import com.sharp.join.tk.mybatis.framework.join.anno.JoinCollection;
import com.sharp.join.tk.mybatis.utils.ReflectionUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import tk.mybatis.mapper.MapperException;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.EntityTable;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.mapper.mapperhelper.MapperTemplate;
import tk.mybatis.mapper.util.MetaObjectUtil;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;

import static tk.mybatis.mapper.util.MsUtil.getMapperClass;

/**
 * Title: JoinProvider
 * Description: Copyright: Copyright (c) 2019 Company: BHFAE
 *
 * @author Sharp
 * @date 2022/7/4 18:14
 */
public class JoinProvider extends MapperTemplate {

    private static final String DEFAULT_RESULT_MAP = "_BaseMapperResultMap";
    protected final Map<Class<?>, ResultMap> resultMap = new ConcurrentHashMap<>();
    protected final Map<Field, ResultMapping> resultMappingMap = new ConcurrentHashMap<>();

    public JoinProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
        super(mapperClass, mapperHelper);
    }

    @Override
    public Class<?> getEntityClass(MappedStatement ms) {
        String msId = ms.getId();
        if (entityClassMap.containsKey(msId)) {
            return entityClassMap.get(msId);
        } else {
            Class<?> mapperClass = getMapperClass(msId);
            Type[] types = mapperClass.getGenericInterfaces();
            for (Type type : types) {
                if (type instanceof ParameterizedType) {
                    ParameterizedType t = (ParameterizedType) type;
                    if (t.getRawType() == this.mapperClass || this.mapperClass.isAssignableFrom((Class<?>) t.getRawType())) {
                        Class<?> returnType = (Class<?>) t.getActualTypeArguments()[0];
                        //获取该类型后，第一次对该类型进行初始化
                        // modify by Sharp for join mapper
                        JoinEntityHelper.initEntityNameMap(returnType, mapperHelper.getConfig());
                        EntityTable entityTable = JoinEntityHelper.getEntityTable(returnType);
                        initResultMapping(ms.getConfiguration(), entityTable);
                        entityClassMap.put(msId, returnType);
                        return returnType;
                    }
                }
            }
        }
        throw new MapperException("无法获取 " + msId + " 方法的泛型信息!");
    }

    private void initResultMapping(Configuration configuration, EntityTable entityTable) {
        LinkedHashSet<EntityColumn> entityColumns = entityTable.getEntityClassColumns();
        if (CollectionUtils.isNotEmpty(entityColumns)) {
            for (EntityColumn entityColumn : entityColumns) {
                String column = entityColumn.getColumn().replace(JoinEntityResolve.SPLIT, JoinEntityResolve.COLUMN_SPLIT);
                Matcher matcher = EntityTable.DELIMITER.matcher(column);
                if (matcher.find()) {
                    column = matcher.group(1);
                }

                String[] propertyNameArray = entityColumn.getProperty().split("\\" + JoinEntityResolve.SPLIT);
                String property = propertyNameArray[propertyNameArray.length - 1];
                ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, entityColumn.getJavaType());
                if (entityColumn.getJdbcType() != null) {
                    builder.jdbcType(entityColumn.getJdbcType());
                }

                if (entityColumn.getTypeHandler() != null) {
                    try {
                        builder.typeHandler(entityTable.getInstance(entityColumn.getJavaType(), entityColumn.getTypeHandler()));
                    } catch (Exception var9) {
                        throw new MapperException(var9);
                    }
                }

                List<ResultFlag> flags = new ArrayList();
                if (entityColumn.isId()) {
                    flags.add(ResultFlag.ID);
                }

                builder.flags(flags);
                resultMappingMap.put(JoinFieldHelper.getField(entityColumn.getEntityField()), builder.build());
            }
        }
    }

    public ResultMap getResultMap(Configuration configuration, EntityTable entityTable) {
        String resultMapId = entityTable.getEntityClass().getTypeName() + DEFAULT_RESULT_MAP;
        ResultMap res = this.resultMap.get(entityTable.getEntityClass());
        if (res != null) {
            return res;
        } else {
            if (MapUtils.isNotEmpty(resultMappingMap)) {
                List<Field> fields = ReflectionUtil.getFields(entityTable.getEntityClass());
                List<ResultMapping> resultMappings = buildResultMappingList(configuration, resultMapId, fields);
                ResultMap.Builder builder = new ResultMap.Builder(configuration, resultMapId, entityTable.getEntityClass(), resultMappings, null);
                saveResultMap(configuration, entityTable.getEntityClass(), builder.build());
                return this.resultMap.get(entityTable.getEntityClass());
            }
        }
        return null;
    }

    private void saveResultMap(Configuration configuration, Class<?> entityClazz, ResultMap resultMap) {
        this.resultMap.put(entityClazz, resultMap);
        configuration.addResultMap(resultMap);
    }

    private List<ResultMapping> buildResultMappingList(Configuration configuration, String resultMapId, List<Field> fields) {
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        for (Field field : fields) {
            ResultMapping resultMapping;
            if (field.isAnnotationPresent(JoinAssociation.class)) {
                ResultMap resultMap = getResultMap(configuration, field, field.getType(), resultMapId);
                resultMapping = buildResultMapping(configuration, resultMap, field.getName(), field.getType());
            } else if (field.isAnnotationPresent(JoinCollection.class)
                    && field.getGenericType() instanceof ParameterizedType
                    && ((ParameterizedType) field.getGenericType()).getActualTypeArguments().length == 1) {
                try {
                    Class<?> fieldActualClazz = Class.forName(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].getTypeName());
                    ResultMap resultMap = getResultMap(configuration, field, fieldActualClazz, resultMapId);
                    resultMapping = buildResultMapping(configuration, resultMap, field.getName(), field.getType());
                } catch (ClassNotFoundException e) {
                    throw new MapperException(e);
                }
            } else {
                resultMapping = resultMappingMap.get(field);
            }
            if (resultMapping != null) {
                resultMappings.add(resultMapping);
            }
        }
        return resultMappings;
    }

    private ResultMap getResultMap(Configuration configuration, Field parentField, Class<?> fieldActualClazz, String parentResultMapId) {
        ResultMap res = this.resultMap.get(fieldActualClazz);
        if (res != null) {
            return res;
        } else {
            String resultMapId = buildResultMapId(parentField, parentResultMapId);
            List<Field> fields = ReflectionUtil.getFields(fieldActualClazz);
            List<ResultMapping> resultMappings = buildResultMappingList(configuration, resultMapId, fields);
            ResultMap.Builder builder = new ResultMap.Builder(configuration, resultMapId, fieldActualClazz, resultMappings, null);
            saveResultMap(configuration, fieldActualClazz, builder.build());
            return this.resultMap.get(fieldActualClazz);
        }
    }

    private String buildResultMapId(Field parentField, String parentResultMapId) {
        StringBuilder builder = new StringBuilder(parentResultMapId);
        builder.append("_[");
        if (parentField.isAnnotationPresent(JoinAssociation.class)) {
            builder.append("association");
        } else if (parentField.isAnnotationPresent(JoinCollection.class)) {
            builder.append("collection");
        }
        builder.append("]");
        builder.append(parentField.getName());
        return builder.toString();

    }

    private ResultMapping buildResultMapping(Configuration configuration, ResultMap resultMap, String fieldName, Class<?> clazz) {
        ResultMapping.Builder builder = new ResultMapping.Builder(configuration, fieldName, null, clazz);
        builder.nestedResultMapId(resultMap.getId());
        return builder.build();
    }

    @Override
    protected void setResultType(MappedStatement ms, Class<?> entityClass) {
        EntityTable entityTable = JoinEntityHelper.getEntityTable(entityClass);
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        ResultMap resultMap = getResultMap(ms.getConfiguration(), entityTable);
        resultMaps.add(resultMap);
        MetaObject metaObject = MetaObjectUtil.forObject(ms);
        metaObject.setValue("resultMaps", Collections.unmodifiableList(resultMaps));
    }

    @Override
    protected String tableName(Class<?> entityClass) {
        String tableName = JoinEntityHelper.getTableName(entityClass);
        if(StringUtils.isEmpty(tableName)) {
            return super.tableName(entityClass);
        } else {
            return tableName;
        }
    }
}
